package state.socket;

import particle.core.tools.ByteBuffer;
import particle.core.tools.PMath;
import state.util.SerialUtil;
import state.util.array.ByteArray;

public class DataPackage {
  public static final int DEFAULT_PACKAGE_SIZE = 10000; // in byte
  private ByteArray data = null; // 4 bytes đầu là header, 4 byte tiếp theo là độ dài, còn lại là data
  private int putDataIndex = 8;
  private int getDataIndex = 8;
  private ByteBuffer byteBuffer = null;

  private DataPackage() {
  }
  
  public static DataPackage createDataPackage(ByteArray dataPackage) {
    DataPackage dataPackage2 = new DataPackage();
    dataPackage2.data = ByteArray.create(dataPackage.len());
    ByteArray.arraycopy(dataPackage, 0, dataPackage2.data, 0, dataPackage2.data.len());
    dataPackage2.putDataIndex = dataPackage.len();
    dataPackage2.expandSize();
    return dataPackage2;
  }
  
  public static DataPackage createDataPackage1(int header) {
    DataPackage dataPackage2 = new DataPackage();
    dataPackage2.data = ByteArray.create(DEFAULT_PACKAGE_SIZE);
    ByteArray.arraycopy(SerialUtil.serialNumberFromInt(header), 0, dataPackage2.data, 0, 4);
    return dataPackage2;
  }
  
  public static DataPackage createDataPackage3(int header, int size) {
    DataPackage dataPackage2 = new DataPackage();
    size = PMath.max(size, 8);
    dataPackage2.data = ByteArray.create(size);
    ByteArray.arraycopy(SerialUtil.serialNumberFromInt(header), 0, dataPackage2.data, 0, 4);
    return dataPackage2;
  }
  
  public void expandSize() {
    ByteArray tmp = ByteArray.create(data.len() * 2);
    ByteArray.arraycopy(data, 0, tmp, 0, data.len());
    data = tmp;
  }
  
  public void expandSizeTo(int size) {
    ByteArray tmp = ByteArray.create(PMath.max(size, data.len() * 2));
    ByteArray.arraycopy(data, 0, tmp, 0, data.len());
    data = tmp;
  }
  
  public int getHeader() {
    return SerialUtil.getInt(data, 0);
  }

  public void setHeader(int header) {
    ByteArray.arraycopy(SerialUtil.serialNumberFromInt(header), 0, data, 0, 4);
  }

  public int getDataLength() {
    return SerialUtil.getInt(data, 4);
  }

  public void putByteArray(ByteArray bytes) {
    if (data.len() - putDataIndex < bytes.len() + 4) {
      expandSizeTo(bytes.len() + 4);
      putByteArray(bytes);
      return;
    }
    
    putInt(bytes.len());
    ByteArray.arraycopy(bytes, 0, data, putDataIndex, bytes.len());
    putDataIndex += bytes.len();
  }

  public boolean hasMoreData() {
    return getDataIndex < putDataIndex;
  }
  
  public boolean hasMoreData1(int value) {
    return putDataIndex - getDataIndex > value;
  }

  public void putInt(int value) {
    if (data.len() - putDataIndex < 4) {
      expandSize();
      putInt(value);
      return;
    }
    ByteArray.arraycopy(SerialUtil.serialNumberFromInt(value), 0, data, putDataIndex, 4);
    putDataIndex += 4;
  }

  public void putDouble(float value) {
    if (data.len() - putDataIndex < 12) {
      expandSize();
      putDouble(value);
      return;
    }
    
    String str = String.valueOf(value);

    // Lấy vị trí của dấu .
    int tmp = str.indexOf('.');
    if (tmp == -1) {
      tmp = str.length();
    }

    // Lấy độ dài số chữ số sau dấu phẩy
    int tmp1 = str.length() - tmp - 1;
    if (tmp1 == -1) {
      tmp1 = 0;
    }

    // Đưa số double lên thành kiểu long
    int tmp3 = 1;
    for (int i = 0; i < tmp1; i++) {
      tmp3 *= 10;
    }
    int tmp2 = (int) (value * tmp3);
    putLong(tmp2);
    putInt(tmp1);
  }

  public void putLong(int value) {
    if (data.len() - putDataIndex < 8) {
      expandSize();
      putLong(value);
      return;
    }
    
    ByteArray.arraycopy(SerialUtil.serialNumberFromLong(value), 0, data, putDataIndex, 8);
    putDataIndex += 8;
  }

  public void putByte(byte value) {
    if (data.len() - putDataIndex < 1) {
      expandSize();
      putByte(value);
      return;
    }
    
    data.set(putDataIndex, value);
    putDataIndex++;
  }
  
  public void putString(String value) {
    if (byteBuffer == null) {
      byteBuffer = new ByteBuffer();
    }
    byteBuffer.appendString(value, ByteBuffer.EncodingUTF8);
    int len = byteBuffer.getLength();
    
    if (data.len() - putDataIndex < len + 4) {
      expandSizeTo(len + 4);
      putString(value);
      return;
    }
    
    ByteArray.arraycopy(SerialUtil.serialNumberFromInt(len), 0, data, putDataIndex, 4);
    for (int i = 0; i < len; i++) {
      data.set(putDataIndex + 4 + i, byteBuffer.getByte(i));
    }
    putDataIndex += (len + 4);
    byteBuffer.clear();
  }

  public int nextInt() {
    if (putDataIndex - getDataIndex < 4) {
      System.out.println("nextInt: No more space in data package: " + getDataIndex);
      return 0;
    }
    
    int value = SerialUtil.getInt(data, getDataIndex);
    getDataIndex += 4;
    return value;
  }

  public int nextLong() {
    if (putDataIndex - getDataIndex < 8) {
      System.out.println("nextLong: No more space in data package: " + getDataIndex);
      return 0;
    }
    
    int value = SerialUtil.getLong(data, getDataIndex);
    getDataIndex += 8;
    return value;
  }

  public byte nextByte() {
    if (putDataIndex - getDataIndex < 1) {
      System.out.println("nextByte: No more space in data package: " + getDataIndex);
      return 0;
    }
    
    byte value = data.get(getDataIndex);
    getDataIndex++;
    return value;
  }

  public String nextString() {
    int len = nextInt();
    if (putDataIndex - getDataIndex < len) {
      System.out.println("nextString: No more space in data package: " + getDataIndex);
      return "";
    }
    
    String value = SerialUtil.getString(data, getDataIndex, len);
    getDataIndex += len;
    return value;
  }

  public float nextDouble() {
    if (putDataIndex - getDataIndex < 12) {
      System.out.println("nextDouble: No more space in data package: " + getDataIndex);
      return 0;
    }
    
    int tmp1 = nextLong();
    int tmp2 = nextInt();
    int tmp3 = 1;
    for (int i = 0; i < tmp2; i++) {
      tmp3 *= 10;
    }
    return tmp1 * 1.0f / tmp3;
  }

  public ByteArray nextByteArray() {
    int len = nextInt();
    
    if (putDataIndex - getDataIndex < len) {
      System.out.println("nextByteArray: No more space in data package: " + getDataIndex);
      return ByteArray.create(0);
    }
    
    ByteArray result = ByteArray.create(len);
    ByteArray.arraycopy(data, getDataIndex, result, 0, len);
    getDataIndex += len;
    return result;
  }

  public ByteArray getAllData() {
    ByteArray.arraycopy(SerialUtil.serialNumberFromInt(putDataIndex - 8), 0, data, 4, 4); // write the length of data to data package
    ByteArray result = ByteArray.create(putDataIndex);
    ByteArray.arraycopy(data, 0, result, 0, putDataIndex);
    return result;
  }
  
  public void resetReadHeader() {
    getDataIndex = 8;
  }
  
  public void resetPutHeader() {
    putDataIndex = 8;
    getDataIndex = 8;
  }

  public Object clone() {
    return DataPackage.createDataPackage(data);
  }

  public void detroy() {
    data = null;
  }
}
